
#include "dev8030.h"
#include "list.h"
#include "rpc_node.h"
#include "usb_event_list.h"
#include "usbpack.h"
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>

#define SDIO_DEBUG           0
#define SDIO_USE_BLOCK_ALIGN 1

typedef struct {
    int     working_id;
    int     sdio_fd;
    int     error_no;
    void*   plist;
    uint8_t rcv_buff[20 * 1024];
} sdio8030_run_info;

typedef struct {
    char             sdio_file_name[128];
    int              started_flg;
    struct list_head node;

    sdio8030_run_info dev;
} sdio8030_info;

typedef struct sdiorpc_ctrl {
    dev8030          basedev;
    struct list_head head; ///< 下挂的sdio设备
    pthread_mutex_t  mtx;
    int              working_cnt;
} sdiorpc_ctrl;

static int sdio_write(void* handle, unsigned char* buff, int len, int timeout)
{
    sdio8030_run_info* psdio = (sdio8030_run_info*)handle;

    if (psdio->error_no) {
        return -1;
    }

#if SDIO_DEBUG
    printf("sdio : try write len = %d\n", len);
#endif

    while (1) {
        int realwr = len;
#if SDIO_USE_BLOCK_ALIGN
        if (len <= 512) {
            realwr = len;
        } else {
            realwr = (len + 511) / 512 * 512;
            memset(buff + len, 0, realwr - len);
        }
#endif
        int ret = write(psdio->sdio_fd, buff, realwr);

        if (ret == realwr) {
#if SDIO_DEBUG
            printf("sdio : write len = %d\n", ret);

            usbpack pack;
            ret = unpack_usb_pack(buff, len, 0, &pack);
            if (ret) {
                printf("write pack error!!!\n");
            }
            // disp_hex(buff, len);
#endif
            return ret;
        } else {
            printf("sdio : write err = %d\n", ret);
        }

        struct pollfd pfd = {
            .fd      = psdio->sdio_fd,
            .events  = POLLOUT | POLLERR,
            .revents = 0,
        };

        int pollret = poll(&pfd, 1, 1);
        if (pollret < 0) {
            continue;
        }

        if (pfd.revents & POLLERR) {
            psdio->error_no = -1;
            printf("sdio wr : pollerr!!\n");
            return -1;
        }

        if (pfd.revents & POLLOUT) {
            continue;
        }
    }

    return 0;
}

static int sdio_ev_loop(void* handle)
{
    sdio8030_run_info* p8030 = (sdio8030_run_info*)handle;

    struct pollfd pfd = {
        .fd      = p8030->sdio_fd,
        .events  = POLLIN | POLLERR,
        .revents = 0,
    };

    int ret = poll(&pfd, 1, 100);
    if (ret < 0) {
        return 0;
    }

    if (pfd.revents & POLLERR) {
        p8030->error_no = -1;
        printf("sdio rd: pollerr!!\n");
        return -1;
    }

    if (pfd.revents & POLLIN) {
        int rdlen = read(p8030->sdio_fd, p8030->rcv_buff, sizeof(p8030->rcv_buff));

        if (rdlen <= 0) {
            printf("sdio : read error = %d\n", rdlen);
        } else {
            // read callback
#if SDIO_DEBUG
            printf("sdio : read len = %d\n", rdlen);
#endif
            dev_node_list* plist = (dev_node_list*)p8030->plist;
            if (plist->dev_rd_cb) {
                plist->dev_rd_cb(plist->recv_priv, p8030->rcv_buff, rdlen);
            }
        }
    }
}

static int sdio_dev_name(void* devhandle, char* buff, int len)
{
    if (!devhandle || !buff || len <= 0) {
        return 0;
    }

    if (len >= 5) {
        strcpy(buff, "sdio");
        return 5;
    }
    return 0;
}

static int set_sdio_info(sdio8030_run_info* psdio, int fd)
{
    // set sdio
    psdio->error_no = 0;
    psdio->sdio_fd  = fd;

    // set dev_node_list
    dev_node_list* plist = (dev_node_list*)malloc(sizeof(dev_node_list));
    memset(plist, 0, sizeof(dev_node_list));
    plist->devhandle = psdio;
    plist->dev_send  = sdio_write;
    plist->dev_rd_cb = NULL;
    plist->recv_priv = NULL;
    plist->dev_loop  = sdio_ev_loop;
    plist->devname   = sdio_dev_name;
    plist->running   = run_all;
    psdio->plist     = plist;

    plist->remote_buff_len         = 0;
    plist->remote_buff_max_payload = 0;

    dev_node_list_init(plist);
}

static sdio8030_info* sdiorpc_get_info(dev8030* pdev8030, uint32_t workid)
{
    sdiorpc_ctrl* pctrl = (sdiorpc_ctrl*)pdev8030;

    sdio8030_info* ret = NULL;
    pthread_mutex_lock(&pctrl->mtx);

    sdio8030_info* pinfo = NULL;
    list_for_each_entry (pinfo, &pctrl->head, node, sdio8030_info) {
        if (pinfo->dev.working_id == workid) {
            ret = pinfo;
            break;
        }
    }
    pthread_mutex_unlock(&pctrl->mtx);
    return ret;
}

static void* sdiorpc_get_plist(dev8030* pdev8030, uint32_t workid)
{
    sdiorpc_ctrl* pctrl = (sdiorpc_ctrl*)pdev8030;

    sdio8030_info* p8030 = sdiorpc_get_info(pdev8030, workid);

    if (p8030) {
        return p8030->dev.plist;
    }

    return NULL;
}

static const char* testsdiomac = "testsdiomac";

static int sdiorpc_fill_serial(dev8030* pdev8030, uint32_t workid, uint8_t* buff, size_t len)
{
    sdiorpc_ctrl* pctrl = (sdiorpc_ctrl*)pdev8030;

    if (!pctrl || !buff || len <= 0) {
        return -1;
    }

    sdio8030_info* p8030 = sdiorpc_get_info(&pctrl->basedev, workid);

    if (!p8030) {
        return -1;
    }

    if (!p8030->started_flg) {
        return -1;
    }

    int sl = strlen(testsdiomac);
    if (sl <= len) {
        memcpy(buff, testsdiomac, sl);
        return sl;
    }

    return 0;
}

static int sdiorpc_getid_all(dev8030* pdev8030, uint32_t* ids, size_t sz)
{
    sdiorpc_ctrl* pctrl = (sdiorpc_ctrl*)pdev8030;

    if (!pctrl || !ids || sz <= 0) {
        return 0;
    }

    pthread_mutex_lock(&pctrl->mtx);
    sz = sz > pctrl->working_cnt ? pctrl->working_cnt : sz;

    sdio8030_info* psdio = NULL;
    int            cnt   = 0;
    list_for_each_entry (psdio, &pctrl->head, node, sdio8030_info) {
        if (!psdio->started_flg) {
            continue;
        }

        ids[cnt] = psdio->dev.working_id;
        if (++cnt >= sz) {
            break;
        }
    }
    pthread_mutex_unlock(&pctrl->mtx);
    return cnt;
}

static int sdiorpc_getsz(dev8030* pdev8030)
{
    sdiorpc_ctrl* pctrl = (sdiorpc_ctrl*)pdev8030;
    return pctrl->working_cnt;
}

static int start_sdio_proc(sdio8030_info* psdio)
{
    int fd = open(psdio->sdio_file_name, O_RDWR);
    if (fd < 0) {
        return -1;
    }

    set_sdio_info(&psdio->dev, fd);

    return 0;
}

static int stop_sdio(sdio8030_info* psdio)
{
    dev_node_force_exit(psdio->dev.plist);

    close(psdio->dev.sdio_fd);
}

static int sdiorpc_8030_poll(dev8030* pdev8030)
{
    int evt = 0;

    sdiorpc_ctrl* pctrl = (sdiorpc_ctrl*)pdev8030;

    pthread_mutex_lock(&pctrl->mtx);

    sdio8030_info* psdio;
    list_for_each_entry (psdio, &pctrl->head, node, sdio8030_info) {
        if (!psdio->started_flg) {
            int ret = start_sdio_proc(psdio);
            if (!ret) {
                pctrl->working_cnt++;
                psdio->started_flg    = 1;
                psdio->dev.working_id = rpc_dev_get_nxt_working_id(pctrl->basedev.pinfo);
                printf("sdio probe ,path = %s , work id = %d\n", psdio->sdio_file_name, psdio->dev.working_id);
            }
        } else if (psdio->dev.error_no) {
            printf("sdio detach ,path = %s,work id = %d\n", psdio->sdio_file_name, psdio->dev.working_id);
            int ret             = stop_sdio(psdio);
            psdio->dev.error_no = 0;
            psdio->started_flg  = 0;
            if (!ret) {
                pctrl->working_cnt--;
            }
        }
    }

    pthread_mutex_unlock(&pctrl->mtx);
    return 0;
}

extern dev8030_act sdio8030_dev_info;

int reg_sdiorpc_platform(rpc_info* prpc, const char** devs, int dev_cnt)
{
    printf("reg sdio 8030 platform\n");

    sdiorpc_ctrl* ctrl = malloc(sizeof(sdiorpc_ctrl));
    rpc_dev_add(prpc, &ctrl->basedev);

    ctrl->basedev.pact  = &sdio8030_dev_info;
    ctrl->basedev.pinfo = prpc;
    pthread_mutex_init(&ctrl->mtx, NULL);

    INIT_LIST_HEAD(&ctrl->head);

    for (int i = 0; i < dev_cnt; i++) {
        sdio8030_info* psdio = malloc(sizeof(sdio8030_info));
        psdio->started_flg   = 0;
        strcpy(psdio->sdio_file_name, devs[i]);
        list_add_tail(&psdio->node, &ctrl->head);
    }

    return 0;
}

dev8030_act sdio8030_dev_info = {
    .name        = "sdio",
    .polldev     = sdiorpc_8030_poll,
    .getplist    = sdiorpc_get_plist,
    .getsz       = sdiorpc_getsz,
    .getid_all   = sdiorpc_getid_all,
    .fill_serial = sdiorpc_fill_serial,
};
